#pragma once

#include <iostream>
#include <unistd.h>
#include <string>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include "nocopy.hpp"
#include "Log.hpp"
#include "InetAddr.hpp"

using namespace log_ns;

// 1. 常量和枚举
// gsockfd : 设置为 -1, 用作初始的 sockfd, 代表一个无效的套接字描述符
// glocalport : 服务器的默认接口, 默认值为 8888
// SOCKET_ERROR 和 BIND_ERROR : 自定义的错误代码, 分别代表套接字创建失败和绑定失败.
static const int gsockfd = -1;
static const uint16_t glocalport = 8888;

enum
{
    SOCKET_ERROR = 1,
    BIND_ERROR
};

// UdpServer user("192.1.1.1", 8899);
// 一般服务器主要是用来进行网络数据读取和写入的。IO的
// 服务器IO逻辑 和 业务逻辑 解耦
// 这段代码定义了一个 UdpServer 类, 用于创建和管理一个 UDP 服务器, 它能够接受客户端的消息并发送相应~
class UdpServer : public nocopy
{
public:
    // 3. 构造函数
    // UdpServer 类的构造函数的初始化了以下成员变量 :
    // _sockfd : 默认初始化为 gsockfd (-1), 表示无效的套接字
    // _localport : 使用传入的端口号 localport 或默认端口 glocalport (8888)
    // isrunning : 初始化为 false , 表示服务器的初始状态是未运行.
    UdpServer(uint16_t localport = glocalport)
        : _sockfd(gsockfd),
          _localport(localport),
          _isrunning(false)
    {
    }

    // 4. InitServer()
    // 创建套接字 : 使用 socket() 函数创建一个 UDP 套接字, AF_INET 表示使用 IPv4 协议, SOCK_DGRAM 表示使用 UDP.
    // 1. 如果创建失败, 记录日志并退出
    // 2. 成功后, 输出日志, 显示套接字描述符 _sockfd.
    // 绑定套接字 : 使用 bind() 函数将套接字与本地地址 (IP 和端口) 绑定
    // local.sin_family = AF_INET : 设置为 IPv4 地址族
    // local.sin_port = htons(_localport) : 将端口号转换为网络字节序
    // local.sin_addr.s_addr = INADDR_ANY : 服务器绑定到任意 IP 地址
    // band() 调用套接字与本地地址绑定, 如果绑定失败, 记录错误日志并退出
    void InitServer()
    {
        // 1. 创建socket文件
        _sockfd = ::socket(AF_INET, SOCK_DGRAM, 0);
        if (_sockfd < 0)
        {
            LOG(FATAL, "socket error\n");
            exit(SOCKET_ERROR);
        }
        LOG(DEBUG, "socket create success, _sockfd: %d\n", _sockfd); // 3

        // 2. bind
        struct sockaddr_in local;
        memset(&local, 0, sizeof(local));
        local.sin_family = AF_INET;
        local.sin_port = htons(_localport);
        // local.sin_addr.s_addr = inet_addr(_localip.c_str()); // 1. 需要4字节IP 2. 需要网络序列的IP -- 暂时
        local.sin_addr.s_addr = INADDR_ANY; // 服务器端，进行任意IP地址绑定

        int n = ::bind(_sockfd, (struct sockaddr *)&local, sizeof(local));
        if (n < 0)
        {
            LOG(FATAL, "bind error\n");
            exit(BIND_ERROR);
        }
        LOG(DEBUG, "socket bind success\n");
    }

    // 5. Start()
    // Start() 方法用于接收数据并发送响应
    // _isrunning = true : 启动服务器, 进入接收数据的循环
    // recvfrom() : 接收 UDP 数据包, 数据存放在 inbuffer 中, peer 存储发送方的地址信息
    // 如果接收成功 (n > 0), 则:
    // 1. 创建一个 InetAddr 对象, 解析发送对方的 IP 地址和端口
    // 2. 输出接收到的消息及其来源 IP 和端口
    // 3. 生成一个响应消息 ("[udp_server echo]#" + 接收的消息), 然后使用 sendto() 将其发送回客户端
    // 如果 recvfrom() 失败, 打印错误消息.
    void Start()
    {
        _isrunning = true;
        char inbuffer[1024];
        while (_isrunning)
        {
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            ssize_t n = recvfrom(_sockfd, inbuffer, sizeof(inbuffer) - 1, 0, (struct sockaddr *)&peer, &len);
            if (n > 0)
            {
                InetAddr addr(peer);
                inbuffer[n] = 0;
                std::cout << "[" << addr.Ip() << ":" << addr.Port() << "]# " << inbuffer << std::endl;

                std::string echo_string = "[udp_server echo] #";
                echo_string += inbuffer;
                sendto(_sockfd, echo_string.c_str(), echo_string.size(), 0, (struct sockaddr *)&peer, len);
            }
            else
            {
                std::cout << "recvfrom ,  error" << std::endl;
            }
        }
    }
    // 6. 析构函数
    // 在 UdpServer 对象销毁时,
    // 关闭套接字 _sockfd, 如果 _sockfd 大于 gsockfd(即有效套接字), 则调用 close() 关闭套接字
    ~UdpServer()
    {
        if (_sockfd > gsockfd)
            ::close(_sockfd);
    }
    // 2. 成员变量
    // _sockfd : 存储套接字文件描述符
    // _localport : 服务器绑定的本地端口号
    // _isrunning : 一个布尔值, 指示服务器是否正在运行
private:
    int _sockfd;
    uint16_t _localport;
    // std::string _localip; // TODO:后面专门要处理一下这个IP
    bool _isrunning;
};


// 这个代码实现了一个简单的 UDP 服务器 (UdpServer 类)，使用套接字进行网络通信。接下来我会详细解释其如何进行网络通信。

// UDP 通信概述
// UDP（用户数据报协议）是一种无连接、不可靠的网络协议。它不像 TCP 那样需要建立连接和维护状态，发送方直接将数据包发送给接收方，接收方也无需回应。UDP 用于一些对实时性要求较高的应用，如视频流、语音通话等。

// 在这段代码中，UdpServer 类通过 UDP 套接字来接收和发送数据。

// 网络通信的步骤
// 创建套接字（InitServer 函数）:

// ::socket(AF_INET, SOCK_DGRAM, 0) 创建一个 UDP 套接字。这里 AF_INET 指定使用 IPv4 协议，SOCK_DGRAM 指定这是一个 UDP 套接字。
// 如果创建套接字失败，程序会通过日志记录错误并退出。
// 绑定套接字（InitServer 函数）:

// 使用 bind 函数将套接字与本地的 IP 地址和端口号绑定。这意味着服务器将监听本地的指定端口，等待接收客户端发送的数据。
// 绑定时，local.sin_family = AF_INET 表明使用 IPv4 协议，local.sin_port = htons(_localport) 设置了绑定的端口，local.sin_addr.s_addr = INADDR_ANY 表示绑定到本机的所有网络接口。
// 接收数据（Start 函数）:

// recvfrom 是 UDP 套接字接收数据的函数。它会阻塞并等待数据的到来，直到接收到来自客户端的数据包。
// recvfrom 参数：
// 第一个参数是套接字 _sockfd。
// 第二个参数是存放接收到数据的缓冲区 inbuffer。
// 第三个参数是缓冲区的大小。
// 第四个参数是接收标志（通常为 0）。
// 第五个参数是客户端的地址信息（struct sockaddr_in *peer），这是一个输出参数，保存了客户端的 IP 和端口信息。
// 第六个参数是地址结构的长度（socklen_t *len）。
// 如果接收到数据，recvfrom 返回的 n 会大于 0，表示成功接收到数据。
// 处理数据：

// 一旦接收到数据，服务器会创建一个 InetAddr 对象，这个对象通过客户端的 peer 地址结构来解析客户端的 IP 地址和端口。
// 接收到的数据会打印出来，包括发送方的 IP 和端口。然后，服务器会将一个响应消息（在原始消息前加上 "[udp_server echo] #") 发送回客户端。
// 发送响应数据（Start 函数）:

// 使用 sendto 函数将响应消息发送回客户端。sendto 参数：
// 第一个参数是套接字 _sockfd。
// 第二个参数是要发送的消息内容（echo_string.c_str()）。
// 第三个参数是消息的长度。
// 第四个参数是发送标志（通常为 0）。
// 第五个参数是目标地址（struct sockaddr_in *peer），即客户端的地址。
// 第六个参数是地址的长度（len）。
// 关闭套接字（析构函数 ~UdpServer()）:

// 当 UdpServer 对象销毁时，程序会检查套接字是否有效（_sockfd > gsockfd），如果有效，则调用 ::close(_sockfd) 关闭套接字，释放资源。
// 网络通信的核心过程
// 客户端发送数据：
// 客户端将数据发送到 UDP 服务器的 IP 地址和端口。数据报文通过网络传输，并到达服务器。

// 服务器接收数据：
// UdpServer 使用 recvfrom 等待接收来自客户端的数据。数据一旦到达，recvfrom 就将数据复制到缓冲区，并返回接收的字节数。

// 服务器处理数据：
// 服务器将客户端的消息打印到控制台，并构造一个带有原始消息的回显响应。然后，服务器将响应消息通过 sendto 发送回客户端。

// 客户端接收数据：
// 客户端会接收到服务器的响应消息，并可以进一步处理或显示它。

// 网络通信的关键点
// 无连接性：UDP 是无连接的协议，服务器不需要事先与客户端建立连接。每个数据包独立地发送和接收。
// 数据包传输：UDP 通过“数据报”方式传输，每个数据包的大小有限（通常是 65507 字节），因此，如果数据包过大需要分片。
// 无确认机制：UDP 不会自动确认数据包是否被成功接收，也不会重传丢失的数据包，因此它比 TCP 更不可靠，但也更加高效，适用于实时性要求高的应用。
// 总结：UdpServer 类实现了一个简单的 UDP 服务器，通过使用 socket() 创建 UDP 套接字，bind() 绑定本地端口，recvfrom() 接收数据，sendto() 发送数据，完成了基础的网络通信功能。